package mybatis.testDemo;

import com.alibaba.druid.pool.DruidDataSource;
import context.CompontScan;
import lombok.extern.slf4j.Slf4j;
import mybatis.testDemo.datasource.AbstractCustomRoutingDataSource;
import mybatis.testDemo.MybatisBean;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Assert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import sample.mybatis.dto.UsersTmpDto;
import sample.mybatis.mapper.MapperInterface;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;

@Slf4j
public class MybatisBeanForPG extends MybatisBean {
    /**
     * todo data need reset  by language driver.
     * and add second before method
     */
    @BeforeEach
    public void setUp() {
        log.info("====================================MybatisBeanForPG initilize...");
    }


    @Test
    public void printBean() {
        super.startContext();
        PlatformTransactionManager bean = applicationContext.getBean(PlatformTransactionManager.class);
        org.junit.Assert.assertNotNull(bean);
        CompontScan.printSingleton(applicationContext);
    }

    @Test
    public void transcationInitTest() {
        transactionTestForNew();

    }



    /**
     * 1.test outer update success
     * 2.test inner update fail.
     * <p>
     * 1.test outer update fail
     * 2.test inner update success.
     */
    public void makeDataSourceAndMapper() {

    }
//    public Object[] transactionTestForNewWithAop(){
//        return transactionTestForNew(AopConfig.class);
//    }

    public Object[] transactionTestForNew() {
        return transactionTestForNew(1);
    }

    AbstractCustomRoutingDataSource customRoutingDataSource = null;

    public Object[] transactionTestForNew(Integer index) {
        super.startContext();
        DataSourceTransactionManager bean = applicationContext.getBean(DataSourceTransactionManager.class);
        org.junit.Assert.assertNotNull(bean);
        Assertions.assertTrue(applicationContext.getBeanDefinition("customRoutingDataSourceWithThreadLocal").isPrimary());

        /**
         warning: and SqlSessionFactory 's MapperInterface vs applicationContext.getBean(MapperInterface.class);
         ref is difference,
         */
//        SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");
//        SqlSession sqlSession = sqlSessionFactory.openSession(true);
//        MapperInterface mapper = sqlSession.getMapper(MapperInterface.class);
        MapperInterface mapper = applicationContext.getBean(MapperInterface.class);

        try {
            this.dataReset(mapper, bean.getDataSource(), index,"insert.sql",TestTypes.PG);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        TransactionTemplate transactionTemplate = new TransactionTemplate(bean);
        TransactionTemplate transactionTemplate2 = new TransactionTemplate(bean);
        return new Object[]{mapper, transactionTemplate, transactionTemplate2, bean.getDataSource()};
    }

    @Test
    public void testConfigurationWithMapUnderscoreToCamelCase() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        List<UsersTmpDto> objs = mapper.findObject();
        for (UsersTmpDto object : objs) {
            System.out.println(object);
        }
    }

    @Test
    public void testConfigurationWithMapUnderscoreToCamelCaseReturnMap() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        List<Map> objs = mapper.findObject2();
        for (Object o : objs.get(0).keySet()) {
            String key = (String) o;
            System.out.print(key);
        }
        System.out.println();

    }

    @Test
    public void test() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        List<UsersTmpDto> usersTmpDto = mapper.select1(true);
        System.out.println(usersTmpDto);
//        UsersTmpDto usersTmpDto1 = mapper.select2("select * from users where id =2");
//        System.out.println(usersTmpDto1);
    }

    @Test
    public void testInvokeStaticMethod() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        String s = mapper.invokeStaticMethod("hcj1 ");
        System.out.println(s);
    }

    @Test
    public void testCDDATATag() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        String s = mapper.cDDATATag();
        System.out.println(s);
    }

    @Test
    public void trimParam() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        String s = mapper.trimParam("  helloworld ");
        Assertions.assertEquals(s, "  helloworld ");
        System.out.println(s);
    }

    @Test
    public void select6() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        List<String> list = mapper.select6(new Integer[]{1, 2, 3});
//                List<String> list = mapper.select6(Lists.newArrayList("1", "2", "3"));
        System.out.println(list);
        for (String s : list) {
            System.out.println(s);
        }
    }

    @Test
    public void select5() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        String str = mapper.select5();
        System.out.println(str);
    }


    @Test
    public void delete() {
        // Constants
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        int i = mapper.deleteById1();
        System.out.println(i);
    }

    /*might be wait forever **/
    public void test1Connection() {
        Object[] objects = transactionTestForNew();
        // use the managedfactort
        SqlSessionFactory sessionFactory = applicationContext.getBean(SqlSessionFactory.class);
        // get dbConnection when connection not  only one ,and
        DruidDataSource dataSource = applicationContext.getBean("pgDruidDataSource", DruidDataSource.class);
        new Thread(() -> {
            try (SqlSession sqlSession = sessionFactory.openSession()) {
                MapperInterface mapper = sqlSession.getMapper(MapperInterface.class);
                Integer id = mapper.getId();
                System.out.println(id + "demo1");
                Thread.sleep(10000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try (SqlSession sqlSession = sessionFactory.openSession()) {
                // don't
                MapperInterface mapper = sqlSession.getMapper(MapperInterface.class);
                Integer id = mapper.getId();
                System.out.println(id + "demo2");
                Thread.sleep(10000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try (SqlSession sqlSession = sessionFactory.openSession()) {
                // don't
                MapperInterface mapper = sqlSession.getMapper(MapperInterface.class);
                Integer id = mapper.getId();
                System.out.println(id + "demo3");
                Thread.sleep(10000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        System.out.println("end");

//        Timer timer = new Timer();
//        timer.schedule(new TimerTask() {
//            @Override
//            public void run() {
//                System.out.println(dataSource.toString());;
//            }
//        },500l,1000l);
//        new Thread(()->{
//
//        }).start();
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


    @Test
    public void testMultiInsertUsingBatch() {
        Object[] objects = transactionTestForNew();
        SqlSessionFactory sessionFactory = applicationContext.getBean(SqlSessionFactory.class);

        try (SqlSession sqlSession = sessionFactory.openSession(ExecutorType.BATCH, true)) {
            MapperInterface mapper = sqlSession.getMapper(MapperInterface.class);
            List<UsersTmpDto> object = mapper.findObject();
            System.out.println("count before insert " + object.size());
            UsersTmpDto usersTmpDto = new UsersTmpDto();
            usersTmpDto.setId(4);
            usersTmpDto.setName("hcj4");
            usersTmpDto.setUserGroupId(3);

            Integer count = mapper.insertSql(usersTmpDto);
            System.out.println("insert count is :" + count);
            object = mapper.findObject();
            System.out.println("count after insert " + object.size());
            usersTmpDto = new UsersTmpDto();
            usersTmpDto.setId(5);
            usersTmpDto.setName("hcj5");
            usersTmpDto.setUserGroupId(5);
            count = mapper.insertSql(usersTmpDto);
            sqlSession.flushStatements();
            // need to flush

        }
        // use the only  and  same connect

    }

    @Test
    public void testMultiInsert() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        mapper.insertSqlMultiply();
    }

    @Test
    public void OuterSuccess() {
        // session close
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        TransactionTemplate transactionTemplate = (TransactionTemplate) objects[1];
        transactionTemplate.setPropagationBehavior(0);

        transactionTemplate.executeWithoutResult((TransactionStatus status) -> {
            Assert.assertFalse(mapper.findSomeThings());
        });

    }

    @Test
    public void testSelectWithoutReturnTag() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        Integer id = mapper.getId();
        Assertions.assertEquals(id, 1);
        String name = mapper.getName("hcj1 ");
        Assertions.assertEquals(name, "hcj1 ");
        List<Map> maps = mapper.selectListMap();
        System.out.println(maps);


    }

    @Test
    public void testConditionAdjust() {
// use the ognl
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
//        List<UsersTmpDto> list = mapper.selectCondition("hcj1 ","hcj2 ");

//        List<UsersTmpDto> list = mapper.selectCondition2("hcj");
        // print the sql
//        ArrayList<CustomDto> customDtos = Lists.newArrayList(CustomDto.builder().key(3).list(Lists.newArrayList(2,3)).build());
//        List<Map<String, Object>> select = mapper.select(customDtos);
//        System.out.println(select);
//        Map<Integer, ArrayList<Integer>> map = Maps.newHashMap(3, Lists.newArrayList(1, 2));
//        for (Map.Entry<Integer, ArrayList<Integer>> integerArrayListEntry : map.entrySet()) {
//            integerArrayListEntry.getValue()
//        }
//        List<Map<String, Object>> select = mapper.select(map);
        List<UsersTmpDto> list = mapper.selectCondition3("1");
        System.out.println(list);

    }

    @Test
    public void RollbackForFail() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        TransactionTemplate transactionTemplate = (TransactionTemplate) objects[1];
        DataSource dataSources = (DataSource) objects[3];
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.executeWithoutResult((TransactionStatus status) -> {
            Assertions.assertTrue(status.isNewTransaction());

            Assertions.assertTrue(TransactionSynchronizationManager.isSynchronizationActive());

            // update data,
            Connection connection = DataSourceUtils.getConnection(dataSources);
            try {
                connection.setAutoCommit(false);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            PreparedStatement preparedStatement = null;
            try {
                String sql = "update users set user_group_name='党员1' where id='1'";
                preparedStatement = connection.prepareStatement(sql);
                assert preparedStatement != null;
                boolean execute = preparedStatement.execute();
                Assertions.assertFalse(execute);
                String sql1 = "update users set user_group_name='党员1' where id='2'";
                preparedStatement = connection.prepareStatement(sql1);
                execute = preparedStatement.execute();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        });
        // rollback is success!, then rollback to initState
        boolean someThings = mapper.findSomeThings();
        Assertions.assertFalse(someThings);

    }

    static ForkJoinPool forkJoinPool = new ForkJoinPool();

    @Test
    public void OuterSuccessWithRequiredAndInnerFailWithRequiredNew() {

        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        TransactionTemplate transactionTemplate = (TransactionTemplate) objects[1];
        TransactionTemplate transactionTemplate2 = (TransactionTemplate) objects[2];
        DataSource dataSources = (DataSource) objects[3];

        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate2.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        try {
            transactionTemplate.executeWithoutResult((TransactionStatus transactionStatus) -> {
                Connection connection = DataSourceUtils.getConnection(dataSources);
                try {
                    connection.setAutoCommit(false);
                    String sql = "update users set user_group_name='党员1' where id='1'";
                    PreparedStatement preparedStatement = connection.prepareStatement(sql);
                    preparedStatement.execute();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }

                transactionTemplate2.executeWithoutResult((TransactionStatus transactionStatus2) -> {
                    Connection connection2 = DataSourceUtils.getConnection(dataSources);
                    try {
                        connection2.setAutoCommit(false);
                    } catch (SQLException throwables) {
                        throwables.printStackTrace();
                    }
                    Assertions.assertNotEquals(connection, connection2);
                    PreparedStatement preparedStatement = null;
                    for (int i = 2; i <= 3; i++) {
                        int finalI = i;
                        try {
                            String sql = "update users set name=?  where id=?";
                            preparedStatement = connection2.prepareStatement(sql);
                            if (finalI == 3) { // finalI=3 -> 2
                                preparedStatement.setString(1, "hcj 2");
                            } else {
                                // finalI=[2];
                                preparedStatement.setString(1, "hcj " + finalI);
                            }
                            preparedStatement.setInt(2, finalI);
                            preparedStatement.execute();
                        } catch (SQLException throwables) {
                            log.error("===========catch the error msg==================");
                            throwables.printStackTrace();
                            throw new RuntimeException("violate unique key constraint");
                        } finally {
                            try {
                                if (preparedStatement != null) {
                                    preparedStatement.close();
                                }
                            } catch (SQLException throwables) {
                                throwables.printStackTrace();
                            }
                            try {
                                if (connection != null) {
                                    connection.close();
                                }
                                ;
                            } catch (SQLException throwables) {
                                throwables.printStackTrace();
                            }
                        }
                    }
                });
            });

        } catch (Exception e) {
            log.error("===========catch the error msg==================");
            e.printStackTrace();
        }

        Assert.assertFalse(mapper.findSomeThings());
        Assert.assertEquals(mapper.findAnotherThings().toArray(), new Boolean[]{false, false});

    }

    /**
     * 更新
     */
    @Test
    public void transactionTestForNest() {
        Object[] objects = transactionTestForNew();
        MapperInterface mapper = (MapperInterface) objects[0];
        TransactionTemplate transactionTemplate = (TransactionTemplate) objects[1];
        TransactionTemplate transactionTemplate2 = (TransactionTemplate) objects[2];
        DataSource dataSources = (DataSource) objects[3];

        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
        transactionTemplate2.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);

        AbstractPlatformTransactionManager transactionManager = (AbstractPlatformTransactionManager) transactionTemplate2.getTransactionManager();
        transactionManager.setNestedTransactionAllowed(true);

        try {
            transactionTemplate.executeWithoutResult((TransactionStatus transactionStatus) -> {
                Connection connection = DataSourceUtils.getConnection(dataSources);
                try {
                    connection.setAutoCommit(false);
                    String sql = "update users set user_group_name='党员1' where id='1'";
                    PreparedStatement preparedStatement = connection.prepareStatement(sql);
                    preparedStatement.execute();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }

                transactionTemplate2.executeWithoutResult((TransactionStatus transactionStatus2) -> {
                    Connection connection2 = DataSourceUtils.getConnection(dataSources);
                    try {
                        connection2.setAutoCommit(false);
                    } catch (SQLException throwables) {
                        throwables.printStackTrace();
                    }
                    Assertions.assertEquals(connection, connection2);
                    PreparedStatement preparedStatement = null;
                    for (int i = 2; i <= 3; i++) {
                        int finalI = i;
                        try {
                            String sql = "update users set name=?  where id=?";
                            preparedStatement = connection2.prepareStatement(sql);
                            if (finalI == 3) { // finalI=3 -> 2
                                preparedStatement.setString(1, "hcj 2");
                            } else {
                                // finalI=[2];
                                preparedStatement.setString(1, "hcj " + finalI);
                            }
                            preparedStatement.setInt(2, finalI);
                            preparedStatement.execute();
                        } catch (SQLException throwables) {
                            log.error("===========catch the error msg==================");
                            throwables.printStackTrace();
                            throw new RuntimeException("violate unique key constraint");
                        } finally {
                            try {
                                if (preparedStatement != null) {
                                    preparedStatement.close();
                                }
                            } catch (SQLException throwables) {
                                throwables.printStackTrace();
                            }
                            try {
                                if (connection != null) {
                                    connection.close();
                                }
                                ;
                            } catch (SQLException throwables) {
                                throwables.printStackTrace();
                            }
                        }
                    }
                });
            });

        } catch (Exception e) {
            log.error("===========catch the error msg==================");
            e.printStackTrace();
        }

        Assert.assertFalse(mapper.findSomeThings());
        Assert.assertEquals(mapper.findAnotherThings().toArray(), new Boolean[]{false, false});
    }

    /**
     *
     */
    @Test
    public void try1() {
        super.startContext();
        SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        MapperInterface mapper = sqlSession.getMapper(MapperInterface.class);
        List<Map<String, String>> maps = mapper.queryInPg();
        maps.stream().forEach((kv) -> {
            log.info("map:{}", kv.toString());
        });

        List<Map<String, String>> maps2 = mapper.queryJsonBInPg(1);
// use ?? to escape ?.
        maps2.stream().forEach((kv) -> {
            log.info("map2:{}", kv.toString());
        });


    }
}
